
import java.io.*;
import java.net.*;
import java.util.*;
import java.text.SimpleDateFormat;

public class PeerProcess
{	
	static int ThisPeerId;
	
	//Parameters from Common.cfg to be parsed
	static int NumberOfPreferredNeighbors;
	static int UnchokingInterval;
	static int OptimisticUnchokingInterval;
	static String FileName;
	static int FileSize;
	static int PieceSize;
	static int NumberOfPieces;
	static ArrayList<Integer> InterestedNeighbors;
	static ArrayList<Integer> UnChokedNeighbors;
	static int OptimisticUnchokedNeighbor;
	
	//Parameters from PeerInfo.cfg to be parsed
	static ArrayList<PeerInfo> peersList;
	static PeerBitmap PeerBitmapList;
	static ArrayList<PeerInfo> peersToConnect;
	
	//ArrayList of PeerIDs
	static ArrayList<Integer> indexByID;
	
	//ArrayList of received Counts
	static ArrayList<Integer> receivedChunks;
	
	//ArrayList of ArrayLists of have messages
	static ArrayList<ArrayList<Integer>> haveMsgsToTransmit;
	
	static Splitter mySplitter;
	
	//ArrayList maintaining the list of peers that do not have the complete file
	static ArrayList<Integer> incompletePeers;
	
	//for log file
    static FileWriter logFile;
    static String log;
	
    //Variable to indicate program termination
    static boolean hasEnded;
    
	public static void main(String argv[]) throws Exception
	{
		if(argv.length != 0 && argv[0].length() > 0)
			ThisPeerId = Integer.parseInt(argv[0]);
		else
		{
			System.out.println("Did not assign peerId properly");
			System.exit(0);
		}
		
		// Set the port number
		int port = 3456;

		incompletePeers = new ArrayList<Integer>();
		hasEnded = false;
		
		//Parse Common.cfg
		parseCommonConfig();
		
		NumberOfPieces = PeerProcess.FileSize / PeerProcess.PieceSize;
		
		//Size of the last piece
		int lastPartSize = PeerProcess.FileSize % PeerProcess.PieceSize;
		if(lastPartSize >0)
			NumberOfPieces += 1;
		
		//Create the bitmapHash
		PeerBitmapList = new PeerBitmap(NumberOfPieces);
		
		//Parse PeerInfo.cfg
		parsePeerInfoConfig();
				
		mySplitter = new Splitter();
		
		//Initialize listening port and split file
		for(int i=0; i<peersList.size(); i++)
		{
			if(peersList.get(i).getPeerID() == ThisPeerId)
			{
				port = peersList.get(i).getListeningPort();
				if(peersList.get(i).getHasFile())
					mySplitter.split();
			}
		}
		
		//Initialize ArrayLists
		InterestedNeighbors = new ArrayList<Integer>();
		UnChokedNeighbors = new ArrayList<Integer>();
		OptimisticUnchokedNeighbor = -1;
		
		//creates the log file
		startLogFile();
		log = new String();
		
		// Establish the listen socket
		ServerSocket server = new ServerSocket(port);
		
		//Connect to all peers preceding this peer
		for(int i=0; i<peersToConnect.size(); i++)
		{
			try
			{
				Socket clientSocket = new Socket(peersToConnect.get(i).getHostname(), peersToConnect.get(i).getListeningPort());
				
				//create a PeerConnection
				PeerConnection connection = new PeerConnection(clientSocket, 1);
				Thread thread = new Thread(connection);
				thread.start();
			}
			catch(Exception e)
			{
				System.out.println("Could not initiate Connection to "+peersToConnect.get(i).getPeerID());
				e.printStackTrace();
			}
		}
		
		//Create a Timer object that will re-evaluate the unchoked lists;
		Timer unchokeTimer = new Timer(NumberOfPreferredNeighbors, UnchokingInterval, OptimisticUnchokingInterval);
		Thread timerThread = new Thread(unchokeTimer);
		timerThread.start();

		int numberOfReceivingConnections = peersList.size() - peersToConnect.size() - 1;
		
		// Process connection requests
		while (numberOfReceivingConnections > 0)
		{
			// Listen for a TCP connection request
			Socket cSock = server.accept();

			numberOfReceivingConnections--;
			
			//create a PeerConnection
			PeerConnection connection = new PeerConnection(cSock, 0);
			Thread thread = new Thread(connection);
			thread.start();
				
		}// end while
		
		//Wait till program has ended
		while (hasEnded == false);
		
	}// end main
	
	private final static void parseCommonConfig()
	{
		File commonConfigFile = new File("Common.cfg");	//creates the file
		String[] currentLine;
		try
		{
			Scanner input = new Scanner(commonConfigFile);	//creates a scanner for the file
			
			//NumberOfPreferredNeighbors #
			currentLine = input.nextLine().split(" ");	//splits each line of the file into tokens	
			NumberOfPreferredNeighbors = Integer.parseInt(currentLine[1]);

			//UnchokingInterval #
			currentLine = input.nextLine().split(" ");	//splits each line of the file into tokens
			UnchokingInterval = Integer.parseInt(currentLine[1]);

			//OptimisticUnchokingInterval #
			currentLine = input.nextLine().split(" ");	//splits each line of the file into tokens
			OptimisticUnchokingInterval = Integer.parseInt(currentLine[1]);

			//FileName "String"
			currentLine = input.nextLine().split(" ");	//splits each line of the file into tokens
			FileName = new String(currentLine[1]);

			//FileSize #
			currentLine = input.nextLine().split(" ");	//splits each line of the file into tokens
			FileSize = Integer.parseInt(currentLine[1]);

			//PieceSize #
			currentLine = input.nextLine().split(" ");	//splits each line of the file into tokens
			PieceSize = Integer.parseInt(currentLine[1]);
		}
		catch (FileNotFoundException fnf)
		{
			System.out.println("Error: Common.cfg not found");
		}
		catch (NumberFormatException nfe)
		{
			System.out.println("Error in Common.cfg: ");	//prints the error
			System.out.println(nfe);
			nfe.printStackTrace();
		}		
	} //end parseCommonConfig
	
	private final static void parsePeerInfoConfig()
	{
		//Initialize the peerList and peersToConnect ArrayLists
		peersList = new ArrayList<PeerInfo>();
		peersToConnect = new ArrayList<PeerInfo>();
		
		//Initialize choking & have data structures
		indexByID = new ArrayList<Integer>();
		receivedChunks = new ArrayList<Integer>();
		haveMsgsToTransmit = new ArrayList<ArrayList<Integer>>();
		
		File peerInfoConfigFile = new File("PeerInfo.cfg");	//creates the file
		String[] currentLine;
		try
		{
			Scanner input = new Scanner(peerInfoConfigFile);	//creates a scanner for the file
			
			//Check for the current peer
			boolean currentPeer = false;
			
			//for each peer in the file
			while(input.hasNextLine())
			{
				currentLine = input.nextLine().split(" ");	//splits each line of the file into tokens	
				//gets PeerInfo constructors
				int peerID = Integer.parseInt(currentLine[0]);
				String hostName = currentLine[1];
				int listeningPort = Integer.parseInt(currentLine[2]);
				int hasFile = Integer.parseInt(currentLine[3]);
				peersList.add(new PeerInfo(peerID, hostName, listeningPort, (hasFile==1)));
				
				//Setting the bitmaps initially
				if(hasFile == 1)
					PeerBitmapList.setBitmap(peerID, -1);
				else
				{
					PeerBitmapList.setBitmap(peerID, -2);
					incompletePeers.add(new Integer(peerID));
				}
				
				//Adding the peers to connect to
				if(peerID == ThisPeerId)
					currentPeer = true;
				else
				{
					//for choking and have data structures
					indexByID.add(new Integer(peerID));
					receivedChunks.add(new Integer(0));
					haveMsgsToTransmit.add(new ArrayList<Integer>());
				}
				
				if(currentPeer == false)
				{
					peersToConnect.add(new PeerInfo(peerID, hostName, listeningPort, (hasFile==1)));
				}
				
			} //end for
		} //end try
		catch (FileNotFoundException fnf)
		{
			System.out.println("Error: PeerInfo.cfg not found");
		}
		catch (NumberFormatException nfe)
		{
			System.out.println("Error in PeerInfo.cfg: ");	//prints the error
			System.out.println(nfe);
			nfe.printStackTrace();
		}		
	} //end parsePeerInfoConfig

	public static void startLogFile()
	{
		String toReturn = "";
		//text
		//toReturn += "Created\n";
		try
		{
			logFile = new FileWriter("log_peer_" + ThisPeerId + ".log");
			logFile.write(toReturn);
			logFile.close();
		}
		catch(Exception e)
		{System.out.println(e); e.printStackTrace();}
	}//end startLogFile()

	synchronized public static void logEntry(String message)
	{
		String toReturn = "";
		//the time of the event
		Calendar time = Calendar.getInstance();
		SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		toReturn += format.format(time.getTime());
		//text
		toReturn += ": Peer ";
		//this peers ID
		toReturn += ThisPeerId;
		//message
		toReturn += " " + message + ".\n";
		log += toReturn;
		try
		{
			logFile = new FileWriter("log_peer_" + ThisPeerId + ".log", true);
			logFile.write(toReturn);
			logFile.close();
		}
		catch(Exception e)
		{System.out.println(e); e.printStackTrace();}
	}//end logEntry()
}// end peerProcess

